﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace IWMAG.Scripting.Compiler.UserInfo
{
    public interface IUserInfo2 : IUserInfo
    {
        Rules RuleViolated { get; }
    }

    public abstract class AbstractUserInfo : IUserInfo2
    {
        public int? LineNumber { get; private set; }

        public int? StartIndex { get; private set; }

        public int? Length { get; private set; }

        public bool? CanCompleteBuild { get; private set; }

        public Rules RuleViolated { get; private set; }

        List<string> paramsArgs = new List<string>();

        public AbstractUserInfo(Rules rule, bool? canComplete = true, int? lineNumber = null, int? startIndex = null, int? length = null, params object[] args)
        {
            this.CanCompleteBuild = canComplete;
            this.LineNumber = lineNumber;
            this.StartIndex = startIndex;
            this.Length = length;
            paramsArgs = new List<string>();
            foreach (var item in args)
            {
                paramsArgs.Add(item.ToString());
            }

            this.RuleViolated = rule;


#if DEBUG
            //DO NOT GET RID OF THE FOLLOWING LINE
            //even though it seems to be a waste of resources, it is making sure that the Rule added works in combination with the params object array
            if (!RuleMapping.Get().ContainsKey(RuleViolated))
            {
                throw new Exception(string.Format("Lazy programmers forgot to add rule named `{0}` to the RuleMapping class, curse their black souls.",RuleViolated.ToString()));
            }
            string throwAway = string.Format(RuleMapping.Get()[RuleViolated].Value.Item1, paramsArgs.ToArray());
            
#endif
        }


        public override string ToString()
        {
            if (RuleMapping.Get().ContainsKey(RuleViolated))
            {
                return string.Format(RuleMapping.Get()[RuleViolated].Value.Item1, paramsArgs.ToArray());
            }
            return base.ToString();
            
        }
    }

    public class CompileError2 : AbstractUserInfo
    {
        public CompileError2(Rules rule, int? lineNumber = null, int? startIndex = null, int? length = null, params object[] p)
            : base(rule, false, lineNumber, startIndex, length, p)
        { }

        public CompileError2(Rules rule, bool? canComplete = false, int? lineNumber = null, int? startIndex = null, int? length = null, params object[] p)
            : base(rule, canComplete, lineNumber, startIndex, length, p)
        { }
    }
    public class CompileWarning2 : AbstractUserInfo
    {
        public CompileWarning2(Rules rule, int? lineNumber = null, int? startIndex = null, int? length = null, params object[] p)
            : base(rule, true, lineNumber, startIndex, length, p)
        { }

        public CompileWarning2(Rules rule, bool? canComplete = true, int? lineNumber = null, int? startIndex = null, int? length = null, params object[] p)
            : base(rule, canComplete, lineNumber, startIndex, length, p)
        { }
    }

    public enum Rules
    {
        RedeclaredFunction,
        RedeclaredVariableInScope,
        GlobalVariablesCannotBeDeclaredAsLateBound,
        InvalidTopLevelDeclaration,
        ValueTokenOutsideOfLatebingExpression,
        UnTerminatedStringLiteral,
        WaitStatementArgumentMustBeInteger,
        IncompatibleTypesInAssignment,
        NonLValueInAssignmentAsAssignee,
        ContinueStatementArgumentMustBeInteger,
        BreakStatementArgumentMustBeInteger,
        LogStatementCannotLogVoidValue,
        DoStatementArgumentMustBeBool,
        WhileStatementArgumentMustBeBool,
        CannotAliasOverPrimitiveType,
        AllMembersOfConstantArrayExpressionMustBeOfCompatibleTypes,
        TernaryOperatorFirstArgumentMustBeBool,
        TernaryOperatorResultArgumentsOfIncompatibleType,
        FunctionReturnsValueOfIncompatibleType,
        TypeInferredVariableMustBeInitialized,
        UseOfUnresolvedType,
        IfStatementArgumentMustBeBool,
        UnaryOperatorDoesNotAcceptIncompatibleType,
        DeadCode_Warning
    }


    public class RuleMapping : Dictionary<Rules, Lazy<Tuple<string>>>
    {
        static RuleMapping()
        {
#if !DEBUG
            Get();
            var c = Enum.GetValues(typeof(Rules));
            foreach (Rules item in c)
            {
                if (!_instance.ContainsKey(item))
                {
                    throw new Exception(string.Format("Lazy programmers forgot to add rule named `{0}` to the RuleMapping class, curse their black souls.", item.ToString()));
                }
            }
#endif
        }
        public RuleMapping()
            : base()
        {
            Dictionary<Rules, string> s = new Dictionary<Rules, string> {
            {Rules.RedeclaredFunction,  "Redeclared function `{0}`."}
            ,{Rules.RedeclaredVariableInScope, "Variable `{0}` previously declared in scope."}
            ,{Rules.GlobalVariablesCannotBeDeclaredAsLateBound, "Global variables may not be declared as latebound."}
            ,{Rules.InvalidTopLevelDeclaration, "Invalid top level declaration `{0}`."}
            ,{Rules.ValueTokenOutsideOfLatebingExpression,"Value token outside of latebind expression is meaningless."}
            ,{Rules.UnTerminatedStringLiteral,"Unterminated string literal."}
            ,{Rules.WaitStatementArgumentMustBeInteger, "Argument to wait must be an integer."}
            ,{Rules.IncompatibleTypesInAssignment, "Unable to assign value of type `{0}` to variable of type `{1}`."}
            ,{Rules.NonLValueInAssignmentAsAssignee,"Can only assign a value to variable, property, or element of an array."}
            ,{Rules.ContinueStatementArgumentMustBeInteger, "The optional argument to the continue statement must be an integer."}
            ,{Rules.BreakStatementArgumentMustBeInteger, "The optional argument to the break statement must be an integer."}
            ,{Rules.LogStatementCannotLogVoidValue, "The log statement cannot log a void value."}
            ,{Rules.DoStatementArgumentMustBeBool, "The argument to the do..while statement must be a bool."}
            ,{Rules.WhileStatementArgumentMustBeBool, "The argument to the while statement must be a bool."}
            ,{Rules.CannotAliasOverPrimitiveType, "Cannot create alias over a primitive type."}
            ,{Rules.AllMembersOfConstantArrayExpressionMustBeOfCompatibleTypes, "All elements of constant array must be of type `{0}`."}
            ,{Rules.TernaryOperatorFirstArgumentMustBeBool, "First argument to ternary operator must be of type bool."}
            ,{Rules.TernaryOperatorResultArgumentsOfIncompatibleType , "Second and third argument to ternary operator must be of identical types."}
            ,{Rules.FunctionReturnsValueOfIncompatibleType, "Function is declared as returning `{0}` but instead returns `{1}`."}
            ,{Rules.TypeInferredVariableMustBeInitialized, "Cannot infer type of `{0}`, you must assign it a value during declaration."}
            ,{Rules.UseOfUnresolvedType, "Type `{0}` cannot be found. Are you missing an import?"}
            ,{Rules.IfStatementArgumentMustBeBool, "The argument to an if statement must be a bool."}
            ,{Rules.UnaryOperatorDoesNotAcceptIncompatibleType, "Invalid type for unary operator, expecting `{0}`."}
            ,{Rules.DeadCode_Warning, "Unreachable code detected."}
            };

            foreach (var item in s)
            {
                Add(item.Key, item.Value);
            }
        }

        public void Add(Rules rule, string s)
        {
            Add(rule, new Lazy<Tuple<string>>(() => Tuple.Create(s)));
        }

        private static object syncLock = new object();
        private static RuleMapping _instance;
        public static RuleMapping Get()
        {
            if (_instance == null)
            {
                lock (syncLock)
                {
                    if (_instance == null)
                    {
                        _instance = new RuleMapping();
                    }

                   
                }


            }

            return _instance;
        }
    }
}